﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using System.Data;
using Xant.FlowEngine;
using Xant.FlowEngine.Interface;
using Xant.Utils;
using Dapper;

namespace Xant.OrganzService
{
    public class OrganizationLocator : IOrganizationLocator
    {
        private const int organNoLengthPerLevel = 3; //组织机构树编号每一级码长3位
        private bool lazyLoad = true; //默认开启延迟加载
        private List<Organization> rootOrganizations = new List<Organization>();
        private List<Organization> organizations = new List<Organization>();
        private List<Position> positions = new List<Position>();
        private List<Staff> staffs = new List<Staff>();
        //延迟加载时，记录已经加载过下级组织机构的组织机构集合
        private List<Organization> suborgInquired = new List<Organization>();
        //延迟加载时，记录已经加载过机构成员的组织机构集合
        private List<Organization> staffInquired = new List<Organization>();

        public OrganizationLocator()
        {
            var sLazy = ConfigurationManager.AppSettings["LazyLoad4Organization"];
            if (!string.IsNullOrEmpty(sLazy))
                this.lazyLoad = bool.Parse(sLazy);
            RefreshData();
        }

        public OrganizationLocator(bool lazyLoad)
        {
            this.lazyLoad = lazyLoad;
            RefreshData();
        }

        public IEnumerable<IOrganization> RootOrganizations
        {
            get
            {
                return this.rootOrganizations.AsReadOnly();
            }
        }

        public void RefreshData()
        {
            this.rootOrganizations.Clear();
            this.organizations.Clear();
            this.positions.Clear();
            this.staffs.Clear();
            LoadPositions();//岗位数据很少，所以无视延迟加载选项
            if (lazyLoad)//开启延迟加载时，只加载第一层组织机构目录
            {
                suborgInquired.Clear();
                staffInquired.Clear();
                var sql = "SELECT * FROM sysOrganization WHERE ParentNo='' order by No";
                using (var conn = DBConnectionManager.GetConnection())
                {
                    var organzs = conn.Query<Organization>(sql);
                    foreach (var organz in organzs)
                    {
                        //开启延迟加载时，响应数据访问事件
                        organz.OnSubordinationsInquiring += organization_OnSubordinationsInquiring;
                        organz.OnStaffMembersInquiring += organization_OnStaffMembersInquiring;
                    }
                    this.organizations.AddRange(organzs);
                    this.rootOrganizations.AddRange(organzs);
                }
            }
            else
            {
                using (var conn = DBConnectionManager.GetConnection())
                {
                    //未开启延迟加载时，先将所有职员信息加载到内存中
                    var sql = "select * from sysStaff";
                    this.staffs.AddRange(conn.Query<Staff>(sql));
                    //然后加载组织机构
                    sql = "SELECT * FROM sysOrganization order by No";
                    var organzs = conn.Query<Organization>(sql);
                    //this.organizations.AddRange(organzs);//统一在BuildOrganizationTree()方法中才将组织机构对象添加到内存集合中，效率不会有明显差别
                    //再加载机构-职员关系表
                    sql = "SELECT * FROM sysStaffMember";
                    var members = conn.Query<InnerStaffMember>(sql).ToArray();
                    //最后构造组织机构树
                    BuildOrganizationTree(organzs, members);
                }
            }
        }

        /// <summary>
        /// 构建组织机构树
        /// </summary>
        /// <remarks>注意：此方法不仅会在首次初始化机构树时被调用，还会在补充构建其他客户端新建组织机构时被调用，为了代码的通用性，有一定冗余代码是值得的</remarks>
        /// <param name="organzs">组织机构对象集合，特别注意的是该集合应已按照No字段排序</param>
        /// <param name="members">组织机构成员集合</param>
        private void BuildOrganizationTree(IEnumerable<Organization> organzs, IEnumerable<InnerStaffMember> members)
        {
            var begining = this.organizations.Count == 0;//是否为首次构建组织机构树
            /*将从数据库查询得到的平面数据转化为组织机构目录树*/
            Organization[] nodes = new Organization[16]; //保存各级节点，因为No字段限长50字符，所以最多16级，每级3位。
            foreach (var item in organzs)
            {
                var organz = item;
                int level = organz.No.Length / organNoLengthPerLevel - 1;//返回代码的级数，每级3位。
                Organization orgExisted = null;
                //如果不是首次构建组织机构树，则判断该组织机构节点是否已经存在于内存中
                if (!begining)
                {
                    orgExisted = this.organizations.Find(p => p.Id.Equals(organz.Id));
                }
                //如果存在则使用该对象实例，否则添加到内存集合中
                if (orgExisted != null)
                {
                    organz = orgExisted;
                }
                else
                {
                    this.organizations.Add(organz);
                    if (level == 0)
                    {
                        rootOrganizations.Add(organz);
                    }
                    else
                    {
                        organz.Superior = nodes[level - 1];
                    }
                    //members不为null时填充时该组织机构节点的职员列表，当延迟加载开启时，member参数应传入null
                    if (members != null)
                    {
                        AddStaffMemberToOrganz(organz, members.Where(p => p.OrganizationId.Equals(organz.Id)).OrderBy(p => p.Priority));
                    }
                    //开启延迟加载时，响应数据访问事件
                    if (lazyLoad)
                    {
                        organz.OnSubordinationsInquiring += organization_OnSubordinationsInquiring;
                        organz.OnStaffMembersInquiring += organization_OnStaffMembersInquiring;
                    }
                }
                nodes[level] = organz;
            }
        }

        /// <summary>
        /// 加载指定组织机构节点职员列表
        /// </summary>
        /// <param name="conn">数据库连接</param>
        /// <param name="organz">组织机构节点</param>
        private void LoadStaffMembers(IDbConnection conn, Organization organz)
        {
            if (conn == null)
                throw new ArgumentNullException("conn");
            if (organz == null)
                throw new ArgumentNullException("organz");
            var sql = string.Format("SELECT * FROM sysStaffMember where OrganizationId='{0}' ORDER BY Priority;", organz.Id);
            AddStaffMemberToOrganz(organz, conn.Query<InnerStaffMember>(sql));
        }

        /// <summary>
        /// 添加一组职员到组织机构节点
        /// </summary>
        private void AddStaffMemberToOrganz(Organization organz, IEnumerable<InnerStaffMember> members)
        {
            foreach (var member in members)
            {
                AddStaffMemberToOrganz(organz, member);
            }
        }

        /// <summary>
        /// 添加一个职员到组织机构节点，此方法能避免重复添加
        /// </summary>
        private void AddStaffMemberToOrganz(Organization organz, InnerStaffMember member)
        {
            //避免重复添加
            if (organz.staffMembers.Exists(p => p.Id.Equals(member.Id)))
                return;
            //在已加载的职员集合中查找该职员信息
            var staff = FindStaff(member.StaffId);
            if (staff == null)//没有找到时抛出异常
            {
                throw new ApplicationException(string.Format("未找到员工信息，主键：{0}", member.StaffId));
            }
            var staffMember = new StaffMember()
            {
                Id = member.Id,
                //Organization = organz,//如果不会从职员反过来访问其组织的话，Organization赋不赋值都没什么意义
                Staff = staff,
                Position = this.positions.Find(p => p.Code.Equals(member.PositionCode, StringComparison.CurrentCultureIgnoreCase)),
                IsMainPosition = member.IsMainPosition,
                Priority = member.Priority,
            };
            organz.staffMembers.Add(staffMember);
        }

        /// <summary>
        /// 加载岗位集合
        /// </summary>
        private void LoadPositions()
        {
            positions.Clear();
            var sql = "select * from sysPosition";
            using (var conn = DBConnectionManager.GetConnection())
            {
                positions.AddRange(conn.Query<Position>(sql));
            }
        }

        public IOrganization FindOrganization(object _id, bool autoLoad = true)
        {
            //在此具体实现方案中，组织机构主键为GUID类型
            Guid id = Guid.Parse(_id.ToString());
            var organz = this.organizations.Find(p => p.Id.Equals(id));
            if(organz==null && autoLoad)//延迟加载，或是该组织机构是加载之后才新增的
            {
                var sql = string.Format("SELECT No FROM sysOrganization WHERE Id='{0}'", id);
                using (var conn = DBConnectionManager.GetConnection())
                {
                    var no = conn.ExecuteScalar<string>(sql);
                    organz = LoadOrganizationBranch(conn, no);
                }
            }
            return organz;
        }

        /// <summary>
        /// 加载一个组织机构分支到内存中，即指定组织机构节点以及其所有上级节点，但仅加载该末级节点的职员列表
        /// </summary>
        /// <param name="organNo"></param>
        /// <returns></returns>
        private Organization LoadOrganizationBranch(IDbConnection conn, string organNo)
        {
            if (conn == null)
                throw new ArgumentNullException("conn");
            if (organNo == null)
                throw new ArgumentNullException("organNo");

            var sql = string.Format("SELECT * FROM sysOrganization WHERE '{0}' LIKE CONCAT(No,'%') ORDER BY No;", organNo);
            var organzs = conn.Query<Organization>(sql).ToArray();
            var organz = organzs.LastOrDefault();
            if (organz == null || !organz.No.Equals(organNo, StringComparison.CurrentCultureIgnoreCase))
            {
                //如果数组中最后一个组织机构节点编号与organNo参数值不同则说明指定的组织机构节点不存在
                return null;
            }
            //如果未开启延迟加载，则填充organNo参数对应的组织机构节点下的职员列表
            if (!lazyLoad)
            {
                LoadStaffMembers(conn, organz);
            }
            //构建组织机构树，因为仅加载该末级节点的职员列表，且在上一行代码中已经加载，所以此处member参数传入null
            BuildOrganizationTree(organzs, null);
            return organz;
        }

        /// <summary>
        /// 查找员工
        /// </summary>
        /// <param name="_id">员工ID</param>
        /// <param name="autoLoad">在内存中找不到时，是否自动从数据库加载</param>
        /// <returns></returns>
        public IStaff FindStaff(object _id, bool autoLoad=true)
        {
            //在此具体实现方案中，组织机构主键为GUID类型
            Guid id = Guid.Parse(_id.ToString());
            var staff = this.staffs.Find(p => p.Id.Equals(id));
            if (staff == null && autoLoad)
            {
                using (var conn = DBConnectionManager.GetConnection())
                {
                    staff = LoadStaff(conn, id);
                }
            }
            return staff;
        }

        /// <summary>
        /// 加载指定职员到内存中
        /// </summary>
        /// <param name="conn">数据库连接</param>
        /// <param name="id">职员ID</param>
        /// <returns></returns>
        private Staff LoadStaff(IDbConnection conn, Guid id)
        {
            var sql = "select * from sysStaff where id=@id";
            var staff = conn.Query<Staff>(sql, new { Id = id }).SingleOrDefault();
            if (staff != null)
            {
                var staffExisted = this.staffs.Find(p => p.Id.Equals(staff.Id));
                if (staffExisted != null)
                {
                    //如果内存中已经存在该职员对象，则将新加载的对象属性值复制给已存在的对象
                    staff.CopyTo(staffExisted);
                    staff = staffExisted;
                }
                else
                {
                    this.staffs.Add(staff);
                }
            }
            return staff;
        }

        public IStaffMember FindStaffMember(object _organizId, object _staffId)
        {
            //在此具体实现方案中，主键为GUID类型
            Guid organizId = Guid.Parse(_organizId.ToString());
            Guid staffId = Guid.Parse(_staffId.ToString());
            var organ = FindOrganization(_organizId);
            var staffMember = organ.StaffMembers.Where(p => p.Staff.Id.Equals(staffId)).SingleOrDefault();
            return staffMember;
        }

        public IEnumerable<IOrganization> GetSubordinateOrganizations(object id)
        {
            /*var orgId = (Guid)id;
            IOrganization organ = dictOrgans[orgId];
            if (organ==null)
            {
                organ = QueryOrganization(orgId);
            }*/
            throw new NotImplementedException();
        }

        public IOrganization GetSuperiorOrganization(object Id)
        {
            throw new NotImplementedException();
        }

        //响应下级机构访问事件，延迟到此时再从数据库查询相应数据
        private void organization_OnSubordinationsInquiring(Organization organz)
        {
            if (suborgInquired.Contains(organz))
                return;

            var sql = "SELECT * FROM sysOrganization WHERE ParentNo=@parentNo order by No";
            using (var conn = DBConnectionManager.GetConnection())
            {
                var items = conn.Query<Organization>(sql, new { parentNo = organz.No }).ToArray();
                foreach (var item in items)
                {
                    if (!this.organizations.Exists(p => p.Id.Equals(item.Id)))
                    {
                        //开启延迟加载时，响应数据访问事件
                        item.OnSubordinationsInquiring += organization_OnSubordinationsInquiring;
                        item.OnStaffMembersInquiring += organization_OnStaffMembersInquiring;
                        item.Superior = organz;
                    }
                }
                this.suborgInquired.Add(organz);
            }
        }

        private void organization_OnStaffMembersInquiring(Organization organz)
        {
            if (staffInquired.Contains(organz))
                return;
            using (var conn = DBConnectionManager.GetConnection())
            {
                LoadStaffMembers(conn, organz);
            }
            staffInquired.Add(organz);
        }

        private string GetParentOrganNo(string organNo)
        {
            if (string.IsNullOrEmpty(organNo))
                throw new ArgumentNullException();
            return organNo.Remove(organNo.Length - organNoLengthPerLevel);
        }

    }

    /// <summary>
    /// 内部使用机构-职员关系类，仅为了从数据库读取数据方便
    /// </summary>
    internal class InnerStaffMember
    {
        internal Guid Id { get; set; }
        internal Guid OrganizationId { get; set; }
        internal Guid StaffId { get; set; }
        internal string PositionCode { get; set; }
        internal bool IsMainPosition { get; set; }
        internal int Priority { get; set; }
    }

}
